package com.webchat.ugc.service.redpacket;

import com.webchat.common.constants.RedPacketConstants;
import com.webchat.common.enums.ChatMessageTypeEnum;
import com.webchat.common.enums.RedisKeyEnum;
import com.webchat.common.enums.messagequeue.MessageBroadChannelEnum;
import com.webchat.common.enums.payment.PaymentTransEventEnum;
import com.webchat.common.enums.payment.PaymentTransTypeEnum;
import com.webchat.common.exception.BusinessException;
import com.webchat.common.service.RedisService;
import com.webchat.common.service.messagequeue.producer.MessageQueueProducer;
import com.webchat.common.util.DateUtils;
import com.webchat.common.util.JsonUtil;
import com.webchat.domain.dto.messagecard.MessageCardSendDTO;
import com.webchat.domain.dto.payment.PaymentOrderCreateDTO;
import com.webchat.domain.dto.payment.PaymentTransRequestDTO;
import com.webchat.domain.vo.request.SendRedPacketRequestVO;
import com.webchat.domain.vo.request.mess.ChatMessageRequestVO;
import com.webchat.domain.vo.request.mess.MessageBaseVO;
import com.webchat.domain.vo.response.RedPacketBaseVO;
import com.webchat.domain.vo.response.RedPacketDetailVO;
import com.webchat.ugc.config.MessageCardAccountConfig;
import com.webchat.ugc.config.MessageCardTemplateConfig;
import com.webchat.ugc.repository.dao.IRedPacketDAO;
import com.webchat.ugc.repository.dao.IRedPacketRecordDAO;
import com.webchat.ugc.repository.entity.RedPacketEntity;
import com.webchat.ugc.service.PaymentService;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@RefreshScope
@Service
public class RedPacketService {

    @Autowired
    private IRedPacketDAO redPacketDAO;
    @Autowired
    private IRedPacketRecordDAO redPacketRecordDAO;
    @Autowired
    private RedisService redisService;
    @Autowired
    private PaymentService paymentService;
    @Autowired
    private MessageQueueProducer<Object, Long> messageQueueProducer;
    @Autowired
    private MessageCardAccountConfig messageCardAccountConfig;
    @Autowired
    private MessageCardTemplateConfig messageCardTemplateConfig;
    @Autowired
    private RedissonClient redissonClient;

    @Value("${red-packet.open-with}")
    private String openWith;


    /**
     * 发送包
     *
     * @param sendRedPacketRequest
     * @return
     */
    @Transactional
    public Long send(SendRedPacketRequestVO sendRedPacketRequest) {

        sendRedPacketRequest.setExpireDate(this.getRedPacketExpireDate());
        String sendUserId = sendRedPacketRequest.getSender();
        BigDecimal redPacketAmount = sendRedPacketRequest.getTotalMoney();
        /**
         * 1. 校验发包人账户余额
         */
        paymentService.validateAccountBalance(sendUserId, redPacketAmount);
        /**
         * 2. 申请分布式交易事务id, 申请创建一条交易订单
         */
        String transId = paymentService.transId();
        // 构造支付平台创建订单所需业务参数
        PaymentOrderCreateDTO paymentOrderCreateDTO = this.buildPaymentOrderCreateDTO(sendRedPacketRequest);
        String orderId = paymentService.createOrder(transId, paymentOrderCreateDTO);
        /**
         * 3. 扣除发包人账户余额（实际交易）
         */
        // 构造实际交易请求参数
        PaymentTransRequestDTO paymentTransRequest = this.buildPaymentTransRequestDTO(orderId, sendRedPacketRequest);
        boolean transResult = paymentService.doTrans(paymentTransRequest);
        if (!transResult) {
            // 回滚交易订单
            paymentService.doRollBack(transId);
            throw new BusinessException("发包发送失败，稍后重试！");
        }
        try {
            /**
             * 4. 持久化红包发送记录
             */
            RedPacketEntity redPacketEntity = this.buildRedPacketEntity(orderId, sendRedPacketRequest);
            redPacketEntity = redPacketDAO.save(redPacketEntity);
            /**
             * 5. 刷新红包详情redis缓存
             * 后续红包的信息要在IM客户端展示，为了保证后续请求性能，我们现将红包详情信息做一层redis缓存
             */
            this.refreshRedPacketDetailCache(redPacketEntity);
            this.initOpenRedPacketContextInfo(redPacketEntity);
            /**
             * 6. MQ广播通知websocket推送红包消息给接收人
             */
            MessageBaseVO<RedPacketBaseVO> chatMessage = this.buildMessageVOForSendRedPacket(redPacketEntity);
            messageQueueProducer.broadSend(MessageBroadChannelEnum.QUEUE_RED_PACKET_NOTIFY, chatMessage);
            /**
             * 7. 走“WebChat支付这个服务号推送用户消息认证”
             */
            this.doPushRedPacketSendPaymentOrderMessage(sendRedPacketRequest);
            return redPacketEntity.getId();
        } catch (Exception e) {
            // 交易成功，订单创建成功，但是业务侧处理失败，我们需要回滚用户交易订单
            // 回滚交易订单
            paymentService.doRollBack(transId);
            // 回滚redis数据
            // TODO
        }
        return null;
    }


    private void initOpenRedPacketContextInfo(RedPacketEntity redPacketEntity) {

        Long redPacketId = redPacketEntity.getId();
        int gradCount = redPacketEntity.getCount();
        // 设置红包金额（元）
        BigDecimal totalMoneyYun = redPacketEntity.getTotalMoney();
        // 设置红包金额（分）
        int totalMoneyFen = totalMoneyYun.multiply(new BigDecimal(100)).intValue();
        if (RedPacketConstants.OpenRedPacketWithEnum.LOCK.name().equals(openWith) ||
                RedPacketConstants.OpenRedPacketWithEnum.LUA.name().equals(openWith)) {
            // 初始化红包金额
            OpenRedPacketWithRedissonLockService openRedPacketService =
                    (OpenRedPacketWithRedissonLockService) OpenRedPacketWithFactory.getService(
                            RedPacketConstants.OpenRedPacketWithEnum.LOCK.name());
            openRedPacketService.initBalanceCache(redPacketId, totalMoneyFen);
            openRedPacketService.initGradCountCache(redPacketId, gradCount);
        } else if (RedPacketConstants.OpenRedPacketWithEnum.QUEUE.name().equals(openWith)) {
            // 预先生成好红包份额对应的金额
            OpenRedPacketWithQueueService openRedPacketService =
                    (OpenRedPacketWithQueueService) OpenRedPacketWithFactory.getService(
                            RedPacketConstants.OpenRedPacketWithEnum.QUEUE.name());
            openRedPacketService.preGeneratedRedPackets(redPacketId, totalMoneyFen, gradCount);
        }
    }

    /**
     * 更新红包的状态
     *
     * @param redPacketId
     * @param redPacketStatus
     * @return
     */
    @Transactional
    public boolean updateRedPacketStatus(Long redPacketId, RedPacketConstants.RedPacketStatus redPacketStatus) {

        RedPacketEntity entity = redPacketDAO.findById(redPacketId).orElse(null);
        Assert.notNull(entity, "红包状态更新失败：红包不存在！");
        entity.setStatus(redPacketStatus.getStatus());
        try {
            redPacketDAO.save(entity);
            this.refreshRedPacketDetailCache(entity);
        } catch (Exception e) {
            throw new BusinessException("红包状态更新失败：redis缓存数据刷新异常！");
        }
        return true;
    }

    /**
     * 红包拆分
     *
     * @param redPacketId
     * @param userId
     * @return
     */
    public String open(Long redPacketId, String userId) {
        int amount = OpenRedPacketWithFactory.getService(openWith)
                                .open(redPacketId, userId);
        return new BigDecimal(amount).movePointLeft(2).setScale(2, RoundingMode.HALF_UP).toString();
    }

    /**
     * 红包发送成功，走webchtaPay服务号推送交易凭证消息卡片
     *------------------
     * @param requestVO
     */
    private void doPushRedPacketSendPaymentOrderMessage(SendRedPacketRequestVO requestVO) {

        MessageCardSendDTO messageCardSendDTO = new MessageCardSendDTO();
        messageCardSendDTO.setSender(messageCardAccountConfig.getPayment());
        messageCardSendDTO.setTemplateId(messageCardTemplateConfig.getRedPacketSend());
        messageCardSendDTO.setReceiver(requestVO.getSender());
        messageCardSendDTO.setTitle("扣费凭证");
        messageCardSendDTO.setLogo("https://coderutil.oss-cn-beijing.aliyuncs.com/bbs-image/file_c3a791ab87c5419b980bc27d9791bab6.png");
        messageCardSendDTO.setRedirectName("查看交易明细");
        messageCardSendDTO.setRedirectUrl("https://www.coderutil.com");
        Map<String, Object> vars = new HashMap<>();
        vars.put("amountTitle", "扣费金额");
        vars.put("amount", requestVO.getTotalMoney().toString());
        vars.put("tip", "红包发送，账户变更");
        vars.put("k1", "扣费服务");
        vars.put("k2", "支付方式");
        vars.put("v1", "红包服务");
        vars.put("v2", "WebChatPay线上支付");
        messageCardSendDTO.setVars(vars);
        /**
         * MQ广播通知connect服务，完成服务号消息卡内容websocket主动推送
         */
        messageQueueProducer.broadSend(MessageBroadChannelEnum.QUEUE_MESSAGE_CARD_PUSH, messageCardSendDTO);
    }

    private MessageBaseVO<RedPacketBaseVO> buildMessageVOForSendRedPacket(RedPacketEntity redPacketEntity) {
        MessageBaseVO<RedPacketBaseVO> chatMessage = new ChatMessageRequestVO();
        RedPacketBaseVO redPacketBaseVO = new RedPacketBaseVO();
        chatMessage.setSenderId(redPacketEntity.getSender());
        chatMessage.setReceiverId(redPacketEntity.getReceiver());
        chatMessage.setType(ChatMessageTypeEnum.RED_PACKET.getType());
        chatMessage.setMessageExt(redPacketBaseVO);
        redPacketBaseVO.setId(redPacketEntity.getId());
        redPacketBaseVO.setCover(redPacketEntity.getCover());
        redPacketBaseVO.setBlessing(redPacketEntity.getBlessing());
        return chatMessage;
    }


    private RedPacketDetailVO refreshRedPacketDetailCache(Long redPacketId) {
        RedPacketEntity entity = redPacketDAO.findById(redPacketId).orElse(null);
        return this.refreshRedPacketDetailCache(entity);
    }

    /**
     * 刷新红包缓存
     *
     * @param redPacketEntity
     * @return
     */
    private RedPacketDetailVO refreshRedPacketDetailCache(RedPacketEntity redPacketEntity) {
        if (redPacketEntity == null) {
            return null;
        }
        RedPacketDetailVO redPacketDetailVO = new RedPacketDetailVO();
        BeanUtils.copyProperties(redPacketEntity, redPacketDetailVO);
        redPacketDetailVO.setSendTime(redPacketEntity.getCreateDate().getTime());
        redPacketDetailVO.setExpireTime(redPacketEntity.getExpireDate().getTime());
        // 缓存红包详情
        String cacheKey = this.redPacketCacheKey(redPacketEntity.getId());
        redisService.set(cacheKey, JsonUtil.toJsonString(redPacketDetailVO),
                RedisKeyEnum.RED_PACKET_DETAIL_CACHE.getExpireTime());
        return redPacketDetailVO;
    }


    public RedPacketDetailVO getRedPacket(Long redPacketId) {

        String cacheKey = this.redPacketCacheKey(redPacketId);
        String cache = redisService.get(cacheKey);
        if (StringUtils.isNotBlank(cache)) {
            return JsonUtil.fromJson(cache, RedPacketDetailVO.class);
        }
        /**
         * 虽然红包缓存设置的2 * 24小时，且红包24小时内有效，但是不能完全依赖redis
         * 如果缓存失效，需要主动刷新
         */
        String refreshRedPacketCacheKey = RedisKeyEnum.LOCK_REFRESH_RED_PACKET_CACHE.getKey(String.valueOf(redPacketId));
        RLock lock = redissonClient.getLock(refreshRedPacketCacheKey);
        try {
            lock.lock();
            cache = redisService.get(cacheKey);
            if (StringUtils.isNotBlank(cache)) {
                return JsonUtil.fromJson(cache, RedPacketDetailVO.class);
            }
            return this.refreshRedPacketDetailCache(redPacketId);
        } catch (Exception e) {
            // TODO
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return null;
    }

    private String redPacketCacheKey(Long id) {

        return RedisKeyEnum.RED_PACKET_DETAIL_CACHE.getKey(String.valueOf(id));
    }

    private RedPacketEntity buildRedPacketEntity(String orderId, SendRedPacketRequestVO sendRedPacketRequestVO) {

        RedPacketEntity entity = new RedPacketEntity();
        BeanUtils.copyProperties(sendRedPacketRequestVO, entity);
        entity.setOrderId(orderId);
        entity.setStatus(RedPacketConstants.RedPacketStatus.RUNNING.getStatus());
        return entity;
    }

    /**
     * 构造创建交易订单业务参数
     *
     * @return
     */
    private PaymentOrderCreateDTO buildPaymentOrderCreateDTO(SendRedPacketRequestVO sendRedPacketRequest) {
        PaymentOrderCreateDTO dto = new PaymentOrderCreateDTO();
        dto.setAmount(sendRedPacketRequest.getTotalMoney());
        dto.setSourceAccount(sendRedPacketRequest.getSender());
        dto.setTargetAccount(sendRedPacketRequest.getReceiver());
        dto.setEventType(PaymentTransEventEnum.RED_PACKET.getTransEvent());
        dto.setExpireDate(sendRedPacketRequest.getExpireDate());
        dto.setBillType(PaymentTransTypeEnum.EXPENSES.getTransType());
        dto.setDescription("红包发送订单");
        return dto;
    }

    private Date getRedPacketExpireDate() {
        return DateUtils.addDays(new Date(), 1);
    }

    private PaymentTransRequestDTO buildPaymentTransRequestDTO(String orderId, SendRedPacketRequestVO sendRedPacketRequest) {
        PaymentTransRequestDTO dto = new PaymentTransRequestDTO();
        dto.setOrderId(orderId);
        dto.setAmount(sendRedPacketRequest.getTotalMoney());
        dto.setBillType(PaymentTransTypeEnum.EXPENSES.getTransType());
        dto.setTransDetail("红包发送");
        dto.setTransEvent(PaymentTransEventEnum.RED_PACKET.getTransEvent());
        dto.setSourceUserId(sendRedPacketRequest.getSender());
        dto.setTargetUserId(sendRedPacketRequest.getReceiver());
        return dto;
    }
}
